Глава 15. Создание визуальных компонентов

Одним из показателей квалификации программиста является то, может ли он разрабатывать свои собственные компоненты для визуальной среды программирования вроде C++Builder. Пользовательский компонент должен ничем не отличаться от “настоящего” в том смысле, что его можно будет найти в палитре компонентов, можно будет устанавливать его свойства и события в инспекторе объектов и т. п. Эта глава посвящена написанию пользовательских визуальных компонентов и некоторым сопутствующим вопросам.

Расширение набора ключевых слов

Реализация визуальной среды программирования требует расширения языковых средств, поскольку невозможно на стандартном C++ осуществить, например, механизм свойств. Кроме того, разработчики C++Builder ориентировались на уже существующую библиотеку VCL, разработанную для Delphi и написанную на языке Object Pascal, и потому необходимо было ввести в язык дополнения, необходимые для моделирования чуждых C++ языковых конструкций. В этом разделе мы вкратце опишем ключевые слова C++Builder, так или иначе связанные с визуальным программированием.

_classid

Это ключевое слово обозначает операцию, выполняемую во время компиляции. Синтаксис:

_classid(имя_класса)

Стандартный C++, в отличие от Object Pascal, не может оперировать классами как таковыми. Он оперирует представителями классов, конструированными объектами. Операция _classid позволяет получить, как говорят, указатель на метакласс для специфицированного класса (класс TMetaClass).

Указатель на класс как таковой, безотносительно к его представителям, необходим, например, при регистрации класса компонента. При создании нового компонента C++Builder автоматически генерирует в его модуле такой код:

namespace Cticktape Х

void __fastcail PACKAGE ReaisterO

{

TComponentClass classes[1]=

 

{_classid(CTickTape)};

RegisterComponents("Samples", classes, 0);

}

}

Операция _classid редко используется непосредственно. C++Builder генерирует ее автоматически, как, например, в приведенном фрагменте кода.

_closure

Ключевое слово _closure позволяет объявить специальный указатель (замыкание) на функцию-элемент. В отличие от стандартного указателя на функцию-элемент, объявленного, например, как

void (AClass::*funcPtr)(int);

и привязанного к конкретному классу, в объявлении замыкания класс не указывается и, таким образом, оно может ссылаться на любую функцию-элемент любого класса, имеющую соответствующий список параметр ров. Объявление замыкания отличается от указателя на функцию наличием ключевого слова closure перед именем переменной:

void (_closure *aClosure) (int) ;

SomeClass *some0bj = new SomeClass;

aClosure = someObj->Func;

aClosure (1);

Замыкание комбинирует указатель на функцию-элемент с указателем на конкретный объект, и последний передается в качестве this при вызове функции через замыкание.

Типы событий в классах компонентов являются замыканиями:

typedef void fastcall ( closure *TPassCompleteEvent)

(System::TObject *Sender, bool& stop);

published:

_property TPassCompleteEvent OnPassComplete =

{ read=FOnPassComplete, write=FOnPassComplete };

_property

Ключевое слово _property служит для объявления свойств и спецификации их атрибутов. Синтаксис:

property тип свойства имя = { список атрибутов свойства };

Спецификации атрибутов имеют вид атрибут [= значение] и отделяются друг от друга запятыми. Возможны следующие атрибуты:

read = поле \ get-функция

write = поле \ set-функция

stored = true | false | поле get-функция

index = целая константа

default = значение

nodefault

Для атрибутов read/write может указываться либо ассоциированное со свойством поле (тогда говорят о прямом доступе к свойству), либо имя метода доступа. Если спецификатор write опущен, то свойство можно только читать. Можно, конечно, объявить и свойство только для записи, но трудно придумать случай, когда это имело бы смысл.

Обычным является объявление прямого доступа для чтения и set-функции — для записи:

_property bool Started = {read=FStarted, write=SetStarted};

Атрибут index специфицирует целое число, которое должно передаваться в качестве единственного аргумента get-функции (обычно последняя параметров не имеет) и первого аргумента set-функции (которая обычно имеет один параметр — значение для свойства):

private:

int FCoords [4];

int _fastcall GetCoords(int Index) {

return FCoords[Index];

void _fastcall SetCoords (int Index, int value) FCoords[Index] = value;

}

public:

_property int Left = {read=GetCoords,

write=SetCoords, index=0};

_property int Top = (read=GetCoords,

write=SetCoords, index=l);

_property int Right = (read=GetCoords,

write=SetCoords, index=2} ;

_property int Bottom = {read=GetCoords,

write=SetCoords, index=3};

Атрибуты stored, default и nodefault yназываются спецификаторами хранения

Они не влияют на поведение программы и специфицируются обычно только для опубликованных свойств. В частности, они определяют. будет ли значение свойства сохраняться в файле формы. Если атрибут stored равен true и значение свойства отлично от указанного в атрибуте default, значение свойства сохраняется, в противном случае — нет. Если спецификатор stored опущен, для него принимается значение true.

Атрибут default позволяет указать для свойства значение по умолчанию

_property int TickRate = { read=FTickRate,

write=SetTlcKRate, default=10 );

Значение по умолчанию, заданное атрибутом default, относится только к значению свойства, отображаемому инспектором объектов и не присваивается свойству автоматически при запуске программы. Это значение нужно явно присвоить в конструкторе компонента.

Атрибут nodefault отменяет унаследованное значение свойства по умолчанию.

Атрибуты default и nodefault поддерживаются только для целых типов, перечислений и множеств (класс Set библиотеки VCL).

В C++Builder приняты определенные соглашения об именах свойств, их полей и методов доступа. Этим соглашениям, в частности, следует ClassExplorer при автоматическом генерировании кода для свойств. Если имя свойства, скажем, PropName, то именем поля будет FPropName, именем get-функции GetPropName и именем set-функции — SetPropName.

Опубликование унаследованных свойств

Базовый класс создаваемого компонента может объявлять некоторое свойство как открытое или защищенное. В производном классе компонента можно переобъявить такое свойство:

Кроме того, при переобъявлении свойства в производном классе можно указать новое значение по умолчанию или, специфицировав nodefault, отменить унаследованное значение, не задавая нового.

При переобъявлении свойства указывается только его имя и, возможно, спецификаторы хранения, например:

_published:

_property OldProp = {nodefault};

_published

В разделе класса _published объявляются опубликованные свойства. Этот раздел могут иметь только классы, производные от TObject.

В качестве спецификатора доступа _published эквивалентно public. Разница между ними в том, что опубликованные свойства доступны и в режиме проектирования через инспектор объектов, в то время как к открытым свойствам можно обращаться только программно во время выполнения.

В разделе _published нельзя объявлять конструкторы и деструкторы, а также свойства-массивы. Объявляемые в нем поля должны принадлежать к классовому типу.

_declspec

Применение этого ключевого слова не ограничено визуальным программированием, однако также участвует в поддержке кода VCL.

Общее применение _declspec

Выражение вида _declspec (аргумент) является модификатором, который может применяться к функциям или переменным. Аргументы dllexport, dilimport и thread формируют модификаторы, соответствующие обычным export, _import и _thread. Разница между ними заключается в том, что обычные модификаторы должны всегда непосредственно предшествовать имени объявляемой переменной или функции, в то время как модификаторы _declspec можно помещать в любом месте объявления:

void _declspec(dllexport) f(void);// Верно.

_declspec(dllexport) void f(void);// Верно.

_export void f(void) // Ошибка: должно быть

// void export.

Другими аргументами _declspec общего применения являются:

_declspec(noreturn) void finish(){

...

throw "No return";

}

int RetInt(){

if(....)

return 1;

else

finish(); // Без noreturn генерировалось бы

// предупреждение.

__declspec(nothrow) void f();

void f() throw();

_declspec(property(get = get-функция,

put = put-функция)) объявление_ элемента;

Одна из спецификаций функций доступа может быть опущена. Компилятор транслирует обращения к “элементу данных” в вызовы функций доступа. Возможно также объявление “массива” с одним или несколькими “индексами”:

declspec(property(get=GetVal, put=PutVal)) int vArr[];

Число индексов в обращении к виртуальному массиву должно соответствовать числу параметров у функций доступа. (У простой виртуальной переменной get-функция не имеет ни одного, а put-функция имеет один параметр — присваиваемое значение.) Индексы не обязательно должны быть целыми, они могут быть, например, строками.

Однако инициализация глобальной переменной должна производиться только в одном месте. Если, например, глобальная переменная объявляется и инициализируется в заголовочном файле, подключаемом несколькими файлами проекта, при компоновке возникнет ошибка. Спецификация seiectany решает эту проблему:

_declspec(selectany) int gint = 10;

// Может появляться в

// нескольких файлах проекта.

selectany не может применяться к неинициализируемым переменным и переменным, недоступным за пределами текущего файла (т. е. глобальным статическим, например, глобальным константам C++ без спецификатора external).

_Declspec (uuid("GUID_COM-объекта") )

объявление/определение класса

Применение _declspec с VCL

Перечисленные ниже аргументы _declspec, служащие для поддержки VCL, редко применяются непосредственно. Они используются в макросах, определяемых файлом vcl\sysmac.h.

_thread

Это ключевое слово имеет отношение к VCL лишь постольку, поскольку последняя включает в себя средства для работы с нитями (threads) кода В программе может инициироваться несколько потоков управления исполняющих параллельно. Эти потоки и называются нитями.

Обычно все параллельные нити используют одни и те же глобальные

переменные. Это кстати, позволяет эффективно реализовать взаимодействие между нитями. Объявление глобальной переменной с модификатором _thread приводит к тому, что для каждой нити будет создана своя копия этой переменной.

Модификатор не может применяться к объектам, требующим инициализации во время выполнения (например, переменной — объекту класса с определенным пользователем конструктором).

Пример создания компонента

В этом разделе мы продемонстрируем, как в C++Builder создается визуальный компонент. Это будет компонент телетайпной ленты, отображающий “бегущую строку”. Нечто подобное вы видели в предыдущей главе.

Начало разработки

Разработка специального компонента начинается с создания файлов исходного модуля и заголовка. Это можно сделать, выбрав либо значок Component в диалоге New Items, либо пункт Component | New Component... в меню. Появится диалог, показанный на рис. 15.1.

Рис. 15.1 Диалог New Component

В этом диалоге нужно выбрать из выпадающего списка Ancestor class базовый класс компонента. Мы возьмем в качестве базового TGraphicControl. Далее нужно указать имя нового класса. (Обычно имена классов компонентов начинаются с Т, но мы назвали наш компонент CTickTape.) После этого осталось указать местоположение и имя модуля в поле Unit file name. Нажмите кнопку с многоточием, и появится стандартный диалог Save.

Все поля заполнены (убедитесь, что в поле Palette page указано Samples), и можно нажимать кнопку ОК. В редакторе кода откроется файл CTickTape. срр. Сохраните компонент (кнопкой SaveAll).

Кстати, визуальные компоненты бывают оконные (базовый класс TWinControl) и графические (базовый класс TGraphicControl). Последние, к которым относится и наш компонент, не имеют собственного окна Windows. Они располагаются в пространстве окна своего родительского объекта. Компоненты оконные, как, например, командная кнопка, имеют свое собственное окно.

Код компонента

Создание визуального компонента — процесс сам по себе не визуальный. Все сводится к написанию кода для свойств и методов класса компонента. Здесь вам может очень помочь ClassExplorer, автоматически или, скорее, полуавтоматически генерирующий базовый код. С двумя его диалогами — для полей и методов — вы уже познакомились в предыдущей главе. Правда, чтобы можно было вводить в класс компонента поля, методы и свойства средствами обозревателя классов, нужно сначала создать проект, который будет этот компонент использовать. Создайте новое приложение и присоедините к его проекту модуль CTickTape.cpp (это делается либо в контекстном меню менеджера проектов, либо выбором Project | Add to Project... в главном меню). Созданное приложение станет потом тестовой программой для проверки работоспособности нашего компонента. Пока же с ним ничего делать не нужно.

Registei-() и ValidCtrCheck()

C++Builder уже создал заготовку компонента — файл CTickTape.cpp. Он содержит на данный момент пустой конструктор и две функции — Registerf) и ValidCtrCheck():

static inline void ValidCtrCheck(CTickTape *)

{

new cricKTapenull);

}

//-----------------------

_fastcall CTickTape::CTick(TComponent*Ownet)

: TGraphicControl(Owner) { }

//----------------------------------------

namespace Cticktape {

void _fastcall PACKAGE Register()

(

TComponentClass classes[1] =

{_classid(CTickTape)};

RegisterComponents("Samples", classes, 0);

}

}

Процедура Register () вызывается при регистрации компонента в палитре C++Builder. Первый ее оператор создает массив компонентов (метаклассов), состоящий в нашем случае из единственного элемента.

С помощью ValidCtrCheck () C++Builder проверяет, можно ли создать представитель класса компонента, т. е. не является ли класс абстрактным. Если класс компонента содержит не определенные чистые виртуальные функции, компонент зарегистрирован не будет.

Ввод пользовательского кода

Завершенный код компонента показан в нижеприведенных листингах 15.1 и 15.2.

Листинг 15.1. Класс компонента телетайпной ленты — CTickTape.h

//------------------------------

#ifndef CTickTapeH

#define CTickTapeH

//-------------------------------

#include <SysUtils.hpp>

#include <Controls.hpp>

#include <Classes.hpp>

#include <Forms.hpp>

//-------------------------

typedef void _fastcall (_closure *TPassCoitipleteEvent)

(System::TObject *Sender, boolS stop);

class PACKAGE CTickTape : public TGraphicControl { private:

int FTickRate;

unsigned long FNextTick;

bool FStarted;

bool needsFill;

int storeWidth;

int position;

Graphics::TBitmap* bm;

TPassCompleteEvent POnPassComplete;

void _fastcall SetStartcd(bool value);

void fastcall SetTickRate(int value);

void _fastcall CMChanged(Messages::TmeasageS message) ;

protected:

virtual void _fastcall Setup ();

virtual void_fastcall DoDrawText(TRect rect, long flags);

 

virtual void fastcall Paint();

virtual void fastcall PassComplete();

public:

_fastcall CTickTape(TComponent* Ownersvirtual void fastcall Tick();

_fastcall -CTickTape();

_property unsigned long NextTick = {

read=FNextTick, write=FNextTick };

_property bool Started =

{ read=FStarted, write=SetStarted };

_published:

property Caption;

property Font;

_property ParentFont;

_property int TickRate = { read=FTickRate,

write-SetTickRate, default=10};

_property TPassCompleteEvent OnPassComplete = { read=FOnPassComplete, write^FOnPassComplete };

BEGIN_MESSAGE_MAP

VCL_MESSAGE_HANDLER(CM_FONTCHANGED,

TMessage, CMChanged) ;

VCL_MESSAGE_HANDLER(CM_TEXTCHANGED,

TMessage, CMChanged) ;

END_MESSAGE_MAP(TGraphicControl) ;

};

//---------------------------------

#endif

Листинг 15.2. Модуль компонента — CTickTape.cpp

//-----------------------------------

// CtickTape.срр: Модуль компонента "Бегущая строка".

//

#include <vcl.h>

#pragma hdrstop

#include "CTickTape.h"

#pragma Package (smart_init)

//-----------

//VolidCtrCheck is used to assure that the components

//createddonot have any pure virtual functions.

static in void ValidCtrCheck (CTickTape *)

{

new CTickTape (NULL);

}

//------------------

 

_fastcall CTickTape: :CTickTape (TComponent* Owner)

:TGraphicControl (Owner)

{

Width = 100;

FTickRate = 10;

ControlStyle << csOpaque;

bm = new Graphics::TBitmap ;

Setup ();

}

_fastcall CTickTape::~CTickTape () {

delete bm;

}

//----------------------------

namespace Cticktape {

void _fastcall PACKAGE Register() {

TComponenfcClass classes[1] =

{_classid(CTickTape)};

RegisterComponents("Samples", classes, 0) ;

} I

//------------------------------------

// Установка параметров битовой матрицы.

//

void _fastcall CTickTape::Setup() {

AnsiString text = Caption;

bm->Canvas->Font = Font;

Height = bm->Canvas->TextHeight(text);

storeWidth = Width;

bm->Width = bm->Canvas->TextWidth(text) + 1;

bm->Height = Height;

bm->Canvas->Brush->Color = Color;

bm->Canvas->TextOut(0, 0, text + " ");

if (ComponentState.Contains(csDesigning))

position = 0;

else

position Width;

needsFill = true;

}

void_fastcall CTickTape::DoDrawText(TRect rect, long flags) {

if (Width != storeWidth) Setup() ;

if (needsFill) {

Canvas->Brush->Color = Color;

Canvas->FillRect(rect);

needsFill = false; .

} Canvas->Draw(position, 0, bm) ;

}

void _fastcall CTickTape::Paint() {

TRect rect - ClientRect;

DoDrawText(rect, 0);

}

//---------------------------------

// Сдвиг строки на пиксел, если истек

// очередной интервал ожидания.

//

void _fastcall CTickTape::Tick()

{

if ( ! FStarted || (GetTickCount () < FNextTicK) ) return;

FNextTick += FTickRate;

Repaint () ;

if (~position < - bm->Width) {

position = Width;

PassComplete ();

}

}

//-------------------------------------------

// Set-функции.

//

void _fastcall CTickTape::SetStarted(bool value)

{

if (value == FStarted) return;

FStarted = value;

if (value)

FNextTick = GetTickCount ();

}

void _fastcall CTickTape::SetTickRate (int value)

{

if (value > 0) FTickRate = value;

}

//--------------------------------------------------------

// Отработка сообщений FontCbanged и TextChanged.

//

void _fastcall CTickTape::CMChanged(Messages::

TMessage& message)

{

Setup () ;

Repaint () ;

}

//----------------------------------

// Событие OnPassComplete.

//

void_fastcall CTickTape:: PaseComplite()

{

if (OnPassComplete) { bool stop = false;

OnPassComplete (this", stop);

Started = [stop];

} }

Если вы хотите собственноручно повторить все этапы создания этого компонента, то, воспользовавшись услугами обозревателя классов и руководствуясь листингами, введите в класс следующие элементы:

Не забудьте также ввести код тела конструктора.

В файл CTickTape.h введите typedef замыкания TPassComplete-Event и четыре строки с макросами, которые вы видите в самом конце определения класса. Переобъявите в разделе _published свойства Caption, Font и ParentFont. Это защищенные свойства класса TControl. Если подходить к делу написания компонента совсем серьезно, можно и должно было бы переобъявить и другие унаследованные свойства и, возможно, ввести еще какие-то другие, но для демонстрации этих трех вполне достаточно. Даже ParentFont, пожалуй, лишнее.

Вот, кажется, и все. Сохраните компонент (оба файла). Теперь мы рассмотрим, как он работает.

Разбор кода

Идея работы компонента проста и уже знакома вам по последнему примеру предыдущей главы. Имеется битовая матрица (TBitmap), которая служит для буферизации вывода на экран строки текста. При перерисовывании ее на канву компонента выходящие за его пределы части битовой матрицы автоматически отсекаются. При каждой перерисовке позиция битовой матрицы сдвигается влево на пиксел. Когда строка совсем уйдет с экрана, восстанавливается ее исходная позиция — за правым краем компонента.

Конструктор и деструктор

Конструктор создает операцией new битовую матрицу и устанавливает значения некоторых полей и свойств (Width — унаследованное и уже опубликованное свойство). После этого он вызывает функцию Setup ().

Обратите внимание на строку со свойством ControlStyie. Оно относится к классу Set, который моделирует встроенный тип множеств языка Pascal. Перегруженная операция “ вводит элемент в множество. Стиль компонента определяет, в частности, как будет выполняться его перерисовка. Если не установить csOpaque, весь компонент перед перерисовкой, т. е. вызовом функции Paint (), о которой речь пойдет ниже, будет очищаться и текст строки будет заметно мерцать.

Деструктор удаляет выделенную в конструкторе битовую матрицу.

Метод Setup()

Этот метод производит инициализацию битовой матрицы и вызывается при первоначальном конструировании и изменении свойств компонента — шрифта, ширины или текста.

Метод копирует свойство компонента Font в соответствующее свойство канвы битовой матрицы, определяет ширину и высоту образа текстовой строки и устанавливает по ним размеры матрицы. Устанавливается также цвет фона, и строка выводится на битовую матрицу методом ее канвы TextOut () . Образ строки готов. Справа от текста в битовой матрице имеется по крайней мере одна колонка пикселов, закрашенных фоновым цветом. Благодаря этому обеспечивается очистка пространства компонента, “появляющегося” из-под конца строки при движении ее влево.

Наконец, устанавливается начальная позиция вывода битовой матрицы в графическое пространство компонента. Проверяется свойство Component-State (это множество). Оно индицирует различные аспекты статуса компонента, в частности, участвует ли компонент в визуальном проектировании приложения или он функционирует в работающей программе. Если компонент находится в режиме проектирования, начальная позиция образа строки устанавливается равной 0, т. е. строка будет выведена начиная от его левого края и в конструкторе формы будет виден установленный в инспекторе объектов текст. В противном случае позиции присваивается значение, равное ширине компонента, что помещает строку за его правым краем.

Методы DoDrawTextQ и PaintQ

DoDrawText () производит вывод битовой матрицы на канву компонента, вызывая ее метод Draw () . Попутно он проверяет, не была ли измена ширина компонента и, если это так, вызывает Setup () и очищает компонент, вызывая FillRect (). Методу передается прямоугольник, задающих координаты экранной области компонента (области клиента). Setup(), конечно, производит в данном случае массу лишних операций, но ради простоты логики можно поступиться его изяществом и эффективностью, которая в данном случае не важна. Изменение ширины — единичное редкое действие.

Кстати об эффективности. Вывод образа текста на экран с буферизацией в битовой матрице был выбран потому, что копирование матрицы должно происходить очень быстро, по идее, быстрее, чем непосредственный вывод текста методом Text0ut(). Однако я этого не проверял. Если читатель захочет поэкспериментировать, он может сравнить эффективность методов Draw (), TextOut () и Text?ect (). Это полезное упражнение.

DoDrawText () вызывается из метода Paint (). Виртуальный метод Paint () вызывается, в свою очередь, по сообщениям Windows WM PAINT, a также в результате вызова Refresh () , Repaint (), Update () или Invalidate (). Наш компонент никогда не перерисовывает себя, непосредственно вызывая Paint () . Перед вызовом Paint () производится инициализация канвы компонента и ей выделяется контекст устройства Windows. Без этого наша функция DoDrawText () работать не будет.

Set-функции

Функции SetStartedf) и SetTickRate () устанавливают значения соответствующих свойств. Свойство Started управляет статусом строки — бежит она или стоит на месте. Если его значение меняется с false на true, инициализируется поле маркера времени, свойства NextTick.

Свойство TickRate задает интервал срабатывания “таймера” строки. Set-функция проверяет, не присваивается ли свойству отрицательное число или ноль. Проверка допустимости присваиваемых данных является еще одним важным аспектом механизма свойств.

Метод Tick()

Это единственный, не считая конструктора и деструктора, открытый метод компонента. Именно он обеспечивает “бег” строки по экрану. Если строка остановлена или время нарисовать ее в следующей позиции еще не подошло, метод немедленно возвращает управление. Если же строка запущена и текущий счетчик миллисекунд сравнялся или перешел за маркер времени NextTick, вызывается Repaint () , маркер времени сдвигается на интервал TickRate и определяется следующая позиция строки.

Если строка вышла за пределы компонента, восстанавливается ее начальная позиция и вызывается функция PassComplete () .

Функция PassCompleteQ

Наш компонент может генерировать событие OnPassComplete. Инициирует событие функция PassComplete (). При вызове этой функции из метода Tick() она проверяет, присвоено ли значение (указатель-замыкание) свойству OnPassComplete. Если присвоено, то через свойство вызывается установленная пользователем процедура обработки события со вторым аргументом — переменной stop, в которой пользовательский обработчик может передать true, чтобы остановить строку. Другими словами, если строка ушла с экрана, обработчик события может немедленно запретить возобновление цикла, модифицировав свой параметр-ссылку.

Обработка сообщений

Наш компонент может обрабатывать сообщения cm_fontchanged и CM_TEXTCHANGED, передаваемых ему при изменении соответственно свойств Font и Caption. Это внутренние сообщения приложения С++Вuider, не связанные с сообщениями Windows.

Для обработки обоих этих сообщений мы предусмотрели единственную функцию CMChange() Она вызывает Setup () для обработки изменившихся свойств и затем Repaint () , немедленно перерисовывающую компонент.

Чтобы привязать данные конкретные сообщения к данной процедуре обработки, в классе должен быть реализован виртуальный метод Dis?ат:сЬ (), распределяющий сообщения по назначенным для них обработчикам. В конце определения класса в файле CTickTape.h вы видите четыре макроса “таблицы сообщений”. Они генерируют метод Dispatch () примерно такого вида:

void _fastcali CTickTape::Dispatch(void* Message)

{

switch (((TMessage*)Message)->Msg) { case CM_TEXTCHANGED:

case CM_FOMTCHANGED:

CMChanged(*(TMessage*)Message) ;

breaks; default :

TGraphicControl::Dispatch(Message) ;

} }

Я его несколько “оптимизировал”, поскольку для обоих событий вызывается, одна и та же функция и нет смысла дублировать код для двух меток case. Оператор switch сравнивает идентификатор сообщения Msg с пунктами “таблицы” и вызывает в случае совпадения CMChanged. Если сообщение не опознано, вгдзывается Dispatch () базового класса.

Тестирование компонента

Теперь можно тестировать и отлаживать компонент. Тестировать его как полноценный компонент, установленный в палитре, еще рано. Компоненты отлаживаются сначала в виде обычных модулей, скомпонованных с тестовым приложением. Компонент не размещается на форме с помощью конструктора форм, а создается динамически. Все его начальные свойства также устанавливаются программно, а не инспектором объектов.

Заготовка тестового приложения у вас уже есть. Нужно спроектировать его форму и написать код.

Форма тестера показана на рис. 15.2. Она содержит несколько кнопок и поле редактирования (компонент TEdit). Метка “Текст” слева от поля редактора, собственно, не нужна.

Рис. 15.2 Форма текстового приложения

Что должно тестироваться? В первую очередь, конечно, что строка движется и может быть остановлена и вновь пущена установкой значения свойства Started. Затем нужно проверить, как компонент реагирует на изменения свойств и на сообщение wm_paint, которое посылается приложению, например, при открытии его окна, ранее заслоняемого окном другой программы. Наконец, нужно проверить, генерирует ли компонент предусмотренные события.

Листинг 15.3 показывает код файлов TapeU.h и TapeU.cpp (проект тестера мы назвали Таре).

Листинг 15.3. Файлы программы-тестера для CTickTape

//-------------------------------

// TapeU.h

//

#fndef TapeUH

#define TapeUH

//--------------------------------------------

#include <Classes .hpp>

#include <Controis .hpp>

#include <StdCtrls.hpp>

#include <Forms.hpp>

#include <Dialogs.hpp>

#include "CTickTape.h"

//----------------------------------

class TFormI : public TForm

{

_published:

// IDE-managed Components

TButton *Buttonl;

TLabel *Labell;

TEdit *Editl;

TButton *Buttun2;

TButton *Button3;

TButton *Button4;

TButton *Button5;

TFontDialog *FontDialogl;

TButton *Button6;

CTickTape *Tape;

void _fastcall ButtonlClick(TObject *Sender);

void _fastcall Button2Click(TObject *Sender);

void _fastcall Button3Click (TObject *Sender);

void _fastcall Button4Click(TObject *Sender);

void _fastcall Button5Click(TObject *Sender);

void _fastcall Button6Click(TObject *Sender);

void _fastcall TapePassComplete(TObject *Sender,buul Sstop),

private: // User declarations

public: // User declarations

_fastcall TFormI(TComponent* Owner) ;

// _fastcall -TFormlO;

};

//-------------------------------------

extern PACKAGE TFormI *Forml;

//-------------------------------------

#endif

//-------------------------------------

// TapeU.cpp: Модуль тестера бегущей строки.

//

#include <vcl.h>

#pragma hdrstop

#include "TapeU.h"

//--------------------------------------

#pragma package(smart_init)

#pragma link "CTickTape"

#pragma resource "*.dfm"

TFormI *Forml;

char *strings[3] = ("Первая строка...", "Вторая строка...", "Третья строка...");

//---------------------------------------

_fastcall TFormI::TForm1(TComponent* Owner) : TForm (Ow.ner)

{

/*

Tape = new CTickTape(this);

Tape->Parent = this;

Tape->Left = 20;

Tape->Top = 10;

Tape->OnPassComplete = TapePassComplete;

*/

}

/*

_fastcall TFormI::-TFormI()

{

delete Tape;

}

*/

//-----------------------

 

//кнопка "Выход".

//

_fastcall TFormI::ButtonlClick(TObject *Sender) Tape->Started = false;

Close();

}//------------------

//Кнопка"Ввести".

//

void _fastcall TFormI::Button2Click(TObject *Sender)

{

Tape->Caption = Editl->Text;

}

//--------------------------------

//

void_fastcall TFormI : : Button3Click (TObject *Sender)

{

Tape->Started - true;

while (Tape->Started) {

Application->ProcessMessages() ;

Tape->Tick() ;

}

//-----------------------------------------------

// Кнопка "Стоп".

//

void _fastcall TFormI::Button4Click(TObject *Sender)

{

Tape->Started = false;

} //----------------------

// Кнопка "Ширина".

//

void_fastcall TFormI::Button6Click(TObject *Sender)

{

Tape->Width = Tape->Width % 300 + 50;

}

//----------------------------------------

//----------------------------------------

// Выбор шрифта.

//

void_fastcall TFormI::ButtonSClick(TObject *Sender)

{

FontDialogl->Font = Tape->Font;

if (FontDialogl->Execute ())

Tape->Font = FontDialogl->Font;

//------------------------------------------

// Обработчик события OnPassComplete.

//

void _fastcall TFormI::TapePasaComplete(TObject* Sender,

bool& stop)

static int i = 0;

if (i > 2) {

i = 0;

stop = true;

} Tape->Caption =strings[i++] ;

} //----------------------------------------

В модуле TapeU.cpp вы видите закомментированный код (и одну строку в TapeU.h), выделенный жирным шрифтом, который нужно в данный момент раскомментировать. Показанный в листинге вариант программы (с исключенным деструктором формы и кодом тела конструктора) соответствует окончательному тестированию компонента, установленного в палитру к размещенному на форме тестера во время ее проектирования.

Код программы-тестера

Конструктор формы динамически создает компонент CTickTape и задает начальные значения некоторых свойств, в том числе указатель на процедуру обработки события OnPassComplete. Указатель на компонент, объявленный в классе формы, назван Таре.

Чтобы запустить бегущую строку, нужно нажать кнопку “Старт”. Обравитчик события кнопки устанавливает свойство компонента Started и входит в цикл, который будет прерван только при нажатии кнопки “Стоп”; в общем случае тогда, когда свойство Started окажется равным false.

Код кнопки “Старт” исполняет неопределенный цикл, вызывающий функцию приложения ProcessMessaghes () и метод компонента Tick () . Метод Tick () сам знает, когда нужно будет перерисовать компонент.

Остальная часть программы довольно очевидна. Для управлением шрифтом мы разместили на форме компонент TFontDialog. Компонент этот невизуальный, на форме отображается только его значок.(на рис. 15.3 его можно видеть в левом нижнем углу). Настоящий диалог выбора шрифта заводится на экран, когда вызывается его метод Execute () . Это делает функция Button5Click () (кнопка “Шрифт”).

Тестируется также способность компонента реагировать на ширину своего поля. Размер компонента можно менять, устанавливая свойство Width. Такое действие не генерирует никакого сообщения, однако вызывает функцию Paint ().

Окно редактора и кнопка “Ввести” позволяют присвоить любой текст свойству компонента Label.

Кнопка “Ширина” устанавливает горизонтальный размер компонента.

Пример тестирует также событие компонента OnPassComplete. Как только текущая строка ушла с экрана, по этому событию свойству Caption аписваивается друругая строка. Когда все наличные строки исчерпаны, обработчик события останавливает строку.

Работающий компонент показан на рис. 15.3.

Рис. 15.3 Программа-тестер,

работающая с компонентом CtickTape

Установка компонента

Чтобы установить компонент в палитру компонентов, выберите в меню Component | Install Component... В появившемся диалоге вам нужно будет задать только имя модуля компонента. По умолчанию ваш компонент будет введен в пакет dclusr50.b.pk. Компонент окажется на странице палитры Samples. Если вы не предусматриваете для него специального значка, он будет представлен значком по умолчанию.

Значок компонента

Если вы хотите, чтобы ваш компонент, как и “настоящие”, был представлен в палитре компонентов индивидуальным значком, вы, во-первых, должны предоставить для регистрации сам значок, а также сообщить C+4-Builder сведения о его местоположении. Значок (пиктограмма) должен иметь размер 24Х24 пиксела. “Прозрачным” цветом в C++Builder считается оливковый.

Значок должен находиться в компилированном файле ресурсов с расширением .res или .dcr и иметь идентификатор ресурса, соответствующий имени класса компонента, в нашем случае CTICKTAPE.

Файл ресурсов должен быть присоединен к проекту пакета, в котором будет размещаться компонент. Если ваш компонент имеет стандартное имя, начинающееся с Т, то он может быть загружен автоматически, если файл ресурсов имеет расширение .dcr, находится в том же каталоге, что и модуль компонента и имеет то же имя, что и класс, но без Т. В нашем случае имя класса нестандартное, так что придется либо присоединить значок к проекту пакета с помощью менеджера проекта, либо вручную открыть файл CBuilder5\Lib\dclusr50.bpk и ввести в него примерно такую строку:

USERES("C:\Ppojects\Chl5\TickTape\CTickTape.dcr") ;

Как вы понимаете, все это нужно сделать до того, как вы станете устанавливать компонент.

Окончательное тестирование

Теперь, когда компонент находится в палитре, можно модифицировать программу-тестер (закомментировать код конструктора и деструктор) и разместить компонент на форме визуальным образом, а также установить нужные его свойства в инспекторе объектов. На рис. 15.5 показан размещенный на форме компонент и инспектор объектов, показывающий имеющиеся опубликованные свойства.

Пример применения компонента

Мы хотим здесь продемонстрировать, что наш компонент очень прост в использовании. Форма имеет три кнопки — для запуска, остановки и выхода из программы. Также на ней размещаются три компонента TTickТаре. Для каждого из них можно задать свой размер и шрифт. Свойства TickRate были установлены в инспекторе объектов равными 10, 7 и 15. Код модуля приведен в листинге 15.4.

Рис. 15.4 Компонент конструкторе форм

Листинг 15.4. Файл ThreeU.cpp.

//-------------------------

#include<vcl.h>

#pragma hdstop

#include<ThreeU.h"

//-------------------------

#pragma packege(smart_init)

#pragma link "CTickTape"

#pragma resourse "*.dfm"

TForm1 *Form1;

//---------------------------

_fastcall TFormI: :TForml (.TComponent* Owner) :Form(Owner)

{

}

//---------------------

void _fastcalll TFormI: :Button2Click(TObject *Sender)

{

if(run)return;

CTickTapel->Started = true

CTickTape2->Started = true CTickTape3->Started = true

run = true;

while (run) (

Application->ProcessMessages() ;

if (!run) break;

CTickTapel->Tick()

CTickTape2->Tick()

CTickTape3->Tick() }

} //-----------------------------

void_fastcall TFormI::Button3Click(TObject *Sender) {

if (run) { run = false;

CTickTapel->Started = false;

CTickTape2->Started = false;

CTickTape3->Started = false;

}

}

//----------------------------------

void_fastcall TFo'rmI: :ButtonlClick (TObject *Sender) {

Button3Click(this) ;

Close () ;

} //------------------------------------

В обработчике кнопки “Старт” находится цикл ожидания:

run = true;

while (run) {

Application->ProcessMessages() ;

if (!run) break;

CTickTapel->Tick() ;

CTickTape2->Tick() ;

CTickTape3->Tick() ;

}

При нажатии этой кнопки три строки начинают бежать по экрану, каждая со своей скоростью. Как видите, все, что требуется от программиста — это достаточно часто вызывать метод Tick() строки. Время компонент будет отмерять сам. На рис. 15.5 показано запущенное приложение.

Рис. 15.5 Программа с тремя бегущими строками

Заключение

На этом наше знакомство с C++Builder заканчивается. В этой главе мы рассмотрели один из довольно сложных аспектов визуального программирования — создание визуальных компонентов. Созданный нами компонентов конечно, нельзя считать завершенным. Его нужно было бы дополнить свойствами класса TComponent, такими, как Visible, Enabled и т. д., может быть, придумать и реализовать какие-то специфические свойства и события. Скажем, можно было бы определить событие для двойного щелчка, который бы останавливал и снова запускал строку. Но все это мы оставляем на усмотрение читателя.